[docs] Add analytics and close banner btn#4005
Conversation
There was a problem hiding this comment.
Pull request overview
Adds banner analytics and a dismissible promo banner behavior to the Gesture Handler docs site, with persistence via localStorage for non-landing pages.
Changes:
- Add
localStorage-backed close behavior for the top promo banner on non-landing pages. - Inject an additional GTM container intended for banner tracking.
- Add close button styling for the promo rotator.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/docs-gesture-handler/src/theme/Navbar/index.js | Adds page-type detection and localStorage persistence to conditionally render the promo rotator with a close handler. |
| packages/docs-gesture-handler/src/components/TopPromoRotator/index.tsx | Adds onClose support, exports PROMO_VERSION, and injects a dedicated GTM container script. |
| packages/docs-gesture-handler/src/components/TopPromoRotator/styles.module.css | Styles the new close button. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| useEffect(() => { | ||
| if (typeof window === 'undefined' || typeof document === 'undefined') { | ||
| return; | ||
| } | ||
|
|
||
| const existingScript = document.querySelector<HTMLScriptElement>( | ||
| 'script[src*="www.googletagmanager.com/gtm.js?id=GTM-WV2G3SQL"]' | ||
| ); | ||
|
|
||
| if (existingScript) return; | ||
|
|
||
| (function (w: Window, d: Document, s: string, l: string, i: string) { | ||
| w.dataLayer = w.dataLayer || []; | ||
| w.dataLayer.push({ | ||
| 'gtm.start': new Date().getTime(), | ||
| event: 'gtm.js', | ||
| }); | ||
| const f = d.getElementsByTagName(s)[0] as HTMLScriptElement; | ||
| const j = d.createElement(s) as HTMLScriptElement; | ||
| const dl = l !== 'dataLayer' ? `&l=${l}` : ''; | ||
| j.async = true; | ||
| j.src = `https://www.googletagmanager.com/gtm.js?id=${i}${dl}`; | ||
| f.parentNode?.insertBefore(j, f); | ||
| })(window, document, 'script', 'dataLayer', 'GTM-WV2G3SQL'); | ||
| }, []); |
There was a problem hiding this comment.
This component injects a second GTM container (GTM-WV2G3SQL) at runtime, but the site already loads GTM via @docusaurus/plugin-google-tag-manager (GTM-PHF2NKVT in docusaurus.config.js). Loading two containers on the same dataLayer can lead to duplicated tracking/perf overhead and makes analytics configuration harder to reason about. Consider moving this to the Docusaurus config (or a custom plugin) and/or using a distinct dataLayer name for the dedicated container if it must coexist.
There was a problem hiding this comment.
I assume that both work fine, right @p-malecki?
There was a problem hiding this comment.
Yes, it won't be any problem.
| {onClose && ( | ||
| <button | ||
| type="button" | ||
| className={styles.closeButton} | ||
| aria-label="Close promotion banner" | ||
| onClick={onClose}> | ||
| × | ||
| </button> | ||
| )} |
There was a problem hiding this comment.
The close button is absolutely positioned over the full-width <a> banner without reserving space, so at narrower widths the button can overlap banner text/CTA. Consider reserving right-side padding/margin when onClose is present (e.g., a wrapper modifier class) so content layout doesn’t collide with the close affordance.
| padding: 4px; | ||
| line-height: 1; | ||
| font-size: 16px; | ||
| } |
There was a problem hiding this comment.
.closeButton’s hit area is quite small (roughly 24×24px with current padding/font-size), which is below common touch target guidelines and can be hard to tap on mobile. Consider increasing padding/size (and optionally adding :focus-visible styling) to improve usability and accessibility.
| padding: 4px; | |
| line-height: 1; | |
| font-size: 16px; | |
| } | |
| padding: 8px; | |
| min-width: 32px; | |
| min-height: 32px; | |
| line-height: 1; | |
| font-size: 16px; | |
| } | |
| .closeButton:focus-visible { | |
| outline: 2px solid #001a72; | |
| outline-offset: 2px; | |
| } |
| const [showPromo, setShowPromo] = React.useState(true); | ||
|
|
||
| React.useEffect(() => { | ||
| if (isLanding || typeof globalThis === 'undefined') { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const raw = globalThis.localStorage?.getItem('topPromoState'); | ||
| const state = raw ? JSON.parse(raw) : null; | ||
| if (state?.v === PROMO_VERSION && state?.hidden) { | ||
| setShowPromo(false); | ||
| } | ||
| } catch (_) { | ||
| // ignore | ||
| } | ||
| }, [isLanding]); | ||
|
|
There was a problem hiding this comment.
showPromo is initialized to true and only updated after mount in an effect. If the user previously dismissed the promo, this will still render the banner briefly (UI flash) before setShowPromo(false) runs. Consider initializing state lazily from localStorage (when not landing and in the browser) so the first render matches the persisted hidden state.
| const [showPromo, setShowPromo] = React.useState(true); | |
| React.useEffect(() => { | |
| if (isLanding || typeof globalThis === 'undefined') { | |
| return; | |
| } | |
| try { | |
| const raw = globalThis.localStorage?.getItem('topPromoState'); | |
| const state = raw ? JSON.parse(raw) : null; | |
| if (state?.v === PROMO_VERSION && state?.hidden) { | |
| setShowPromo(false); | |
| } | |
| } catch (_) { | |
| // ignore | |
| } | |
| }, [isLanding]); | |
| const [showPromo, setShowPromo] = React.useState(() => { | |
| // On the landing page, `showPromo` is not used, so default to true. | |
| if (isLanding) { | |
| return true; | |
| } | |
| // Only attempt to read from localStorage in the browser. | |
| if (typeof window === 'undefined' || !window.localStorage) { | |
| return true; | |
| } | |
| try { | |
| const raw = window.localStorage.getItem('topPromoState'); | |
| const state = raw ? JSON.parse(raw) : null; | |
| if (state?.v === PROMO_VERSION && state?.hidden) { | |
| return false; | |
| } | |
| } catch { | |
| // ignore and fall through to default | |
| } | |
| return true; | |
| }); |
Description
Adds a dedicated GTM container for banner tracking and introduces a close option in docs with localStorage persistence. These changes will be replaced by the upcoming banner system in the future.